package com.fineaiops.gateway.controller;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.fineaiops.gateway.bean.Edge;
import com.fineaiops.gateway.bean.LogEntity;
import com.fineaiops.gateway.bean.LogExpEntity;
import com.fineaiops.gateway.bean.ServiceNode;
import com.fineaiops.gateway.service.ExpService;
import com.fineaiops.gateway.service.NodeService;
import com.fineaiops.gateway.util.ConstString;
import com.fineaiops.gateway.util.JsonBuilder;
import com.fineaiops.gateway.util.PageRank;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.sort.SortOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;

@RestController
@CrossOrigin(origins = {"*", "null"})
public class DiagnoseController {
    private final Logger LOG = LoggerFactory.getLogger(getClass());
    @Autowired
    private NodeService nodeService;
    @Autowired
    private ExpService elasticService;

    @RequestMapping(value = "/log_category")
    public String logCategory() {
        //
        return "hello, world";
    }

    @RequestMapping(value = "/diagnose")
    public String diagnose() {
        try {
            List<ServiceNode> services = nodeService.getAllServiceNode();
            for (ServiceNode service : services) {
                String targetServiceID = service.getId();

                SearchHit[] targetCates = elasticService.getExceptionCategories(
                        new String[]{targetServiceID}, new long[]{}, new long[]{});

                List<String> sourceServicesIDs = nodeService.getOriginServiceByID(service.getId());
                double scoreThread = 0;
                for(SearchHit targetCate: targetCates) {
                    Edge logSide = null;
                    double maxScore = -1;
                    for (String sourceServiceID : sourceServicesIDs) {
                        SearchHit[] sourceCates = elasticService.getExceptionCategories(
                                new String[]{sourceServiceID}, new long[]{}, new long[]{});
                        for(SearchHit sourceCate : sourceCates) {
                            double score = getRelativeScore(sourceCate, targetCate);
                            String sourceID = sourceCate.getId();
                            String targetID = targetCate.getId();
                            if (score > scoreThread && score > maxScore ) {
                                maxScore = score;
                                logSide = new Edge(sourceID, targetID);
                            }
                        }
                    }
                    if (logSide != null) {
                        nodeService.updateLogSide(logSide.getSource(), logSide.getTarget(), maxScore);
                    }
                }
            }
            analyseExp();

        } catch (Exception e) {
            LOG.error(e.toString());
            e.printStackTrace();
            return "err";
        }
        return "success";
    }

    @RequestMapping(value = "/handle_log")
    public String handleLog(
            @RequestBody JSONObject data
//            @RequestParam(name = "time") long time,
//            @RequestParam(name = "level") String level,
//            @RequestParam(name = "message") String message,
//            @RequestParam(name = "service") String service,
//            @RequestParam(name = "logger") String logger
    ) {
        LOG.info("begin handle log");
        JSONObject result = new JSONObject();
        result.put("code", ConstString.SUCCESS_DIAGNOSE_LOG_EXIT);
        result.put("msg", "log is exited");
        try {
            long time = (Long)data.get("time");
//            String level = (String)data.get("level");
            String id = (String)data.get("id");
            String message = (String)data.get("message");
            String service = (String)data.get("service");
            String logger = (String)data.get("logger");
            if (elasticService.getLogByID(id).isExists()) {
                return result.toJSONString();
            }
            String cateID = elasticService.getMatchCategories(message, service, logger);
            if (cateID == null) {
                cateID = elasticService.createCategory(time, message, service, logger);
                result.put("code", ConstString.SUCCESS_DIAGNOSE_ADD_CATE);
                result.put("msg","add cate");
            }
            String logID = elasticService.createLog(id, time, message, logger, cateID);
            elasticService.addLogIDtoCategory(cateID, logID, message, time);
        } catch (Exception e) {
            LOG.info("handle log failed");
            LOG.error(e.toString());
            e.printStackTrace();
            result.put("code", ConstString.FAILED_DIAGNOSE);
            result.put("msg", e.toString());
            return "fail";
        }

        LOG.info("handle log success");
        return result.toJSONString();
    }

    public List<String> getCateIDsByServiceID(String id) throws Exception {
        SearchHit[] hits = elasticService.getExceptionCategories(
                new String[]{id}, new long[]{}, new long[]{});
        List<String> res = new ArrayList<>();
        for(SearchHit hit : hits) {
            String cateID = hit.getId();
            res.add(cateID);
        }
        return res;
    }

    public double getRelativeScore(SearchHit source, SearchHit target) throws Exception{
        List<Long> sourceLogSeries = getTimeSeriesV2(source.getId());
        List<Long> targetLogSeries = getTimeSeriesV2(target.getId());
        if (sourceLogSeries.size() == 0 || targetLogSeries.size() == 0) {
            return 0;
        }
        int derta = 1001;
        int recall = 0;
        int curIndex = 0;
        for(long curTime : targetLogSeries) {
            while(curIndex < sourceLogSeries.size() && sourceLogSeries.get(curIndex) < curTime-derta) {
                curIndex++;
            }
            if (curIndex >= sourceLogSeries.size()) {
                break;
            }
            if (sourceLogSeries.get(curIndex) <= curTime) {
                recall++;
                curIndex++;
            }
        }

        return recall * 1.0 / targetLogSeries.size();
    }

    public List<Long> getTimeSeries(SearchHit log) throws Exception {
        JSONArray ids = JSONObject.parseObject(log.getSourceAsString()).getJSONArray("log_ids");
        List<Long> res = new ArrayList<>();
        for(Object id : ids) {
            Map<String, Object> logMap = elasticService.getLogByID((String)id).getSourceAsMap();
             res.add((Long)logMap.get("timestamp"));
        }
        return res;
    }

    public List<Long> getTimeSeriesV2(String cateID) throws Exception {
        List<Long> res = new ArrayList<>();
        List<LogEntity> logs = elasticService.getLogByCategory(cateID, 0, 1000, SortOrder.ASC);
        for (LogEntity log: logs) {
            res.add(log.getTimestamp());
        }
        return res;
    }

    public void analyseExp() throws IOException {
        List<Edge> edges = nodeService.getAllLogEdges();
        List<LogExpEntity> nodes = elasticService.getAllLogCate();
        Map<String, Integer> indexMap = new HashMap<>();
        Map<String, Integer> sumMap = new HashMap<>();
        for (int i = 0; i < nodes.size(); i++) {
            indexMap.put(nodes.get(i).getId(), i);
        }
        for(Edge edge : edges) {
            int sum = sumMap.getOrDefault(edge.getTarget(),0);
            sumMap.put(edge.getTarget(), sum+1);
        }
        int n = nodes.size();
        List<List<Double>> s = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            List<Double> row = new ArrayList<>();
            for (int j = 0; j < n; j++) {
                row.add(0.0);
            }
            s.add(row);
        }

        for(Edge edge : edges) {
            s.get(indexMap.get(edge.getSource()))
                    .set(indexMap.get(edge.getTarget()),
                            1.0/sumMap.get(edge.getTarget()));

        }

        List<Double> prScores = PageRank.calPageRank(0.85, s);
        double maxScore = Double.MIN_VALUE;
        double minScore = Double.MAX_VALUE;
        for (Double score: prScores) {
            maxScore = Double.max(maxScore, score);
            minScore = Double.min(minScore, score);
        }
        Map<String, Double> servicePRmap = new HashMap<>();
        double maxServerScore = Double.MIN_VALUE;
        double minServerScore = Double.MAX_VALUE;
        for (int i = 0; i < prScores.size(); i++) {
            if (maxScore > minScore) {
                nodes.get(i).setScore(0.01 + (prScores.get(i)-minScore) / (maxScore-minScore)*0.99);
            } else {
                nodes.get(i).setScore(0.01);
            }
//            elasticService.updateLogExpScore(nodes.get(i).getId(), nodes.get(i).getScore());
            String serviceID = nodes.get(i).getServiceID();
            double serviceScore = servicePRmap.getOrDefault(serviceID, 0.0)+nodes.get(i).getScore();
            maxServerScore = Double.max(maxServerScore, serviceScore);
            minServerScore = Double.min(minServerScore, serviceScore);
            servicePRmap.put(serviceID,serviceScore);
        }
        nodes.sort((o1, o2) -> o2.getScore().compareTo(o1.getScore()));
        for (int i = 0; i < nodes.size(); i++) {
            if (i < nodes.size() * 0.1) {
                nodes.get(i).setLevel(2);
            } else if (i < nodes.size() * 0.3) {
                nodes.get(i).setLevel(1);
            } else {
                nodes.get(i).setLevel(0);
            }
            elasticService.updateLogExpLevel(nodes.get(i).getId(), nodes.get(i).getLevel());
        }

        for (Map.Entry<String, Double> entry: servicePRmap.entrySet()) {
            double serviceScore = 0.01 + (entry.getValue()-minServerScore) / (maxServerScore-minServerScore) * 0.99;
            nodeService.updateServiceScore(entry.getKey(), serviceScore);
        }

        for (Edge edge: edges) {
            String targetService = nodes.get(indexMap.get(edge.getSource())).getServiceID();
            String sourceService = nodes.get(indexMap.get(edge.getTarget())).getServiceID();
            nodeService.updateServiceSide(sourceService, targetService);
        }

        List<String> rootLogID = nodeService.getBreakdownLog();
        Set<String> rootServiceID = new HashSet<>();
        for(String id: rootLogID) {
            rootServiceID.add(nodes.get(indexMap.get(id)).getServiceID());
        }
        for(String serviceID: rootServiceID) {
            nodeService.updateBreakdownService(serviceID);
        }
    }



    @RequestMapping(value = "/save_err")
    public String saveERR() {
        {
            HashMap<String, String> errToLogMap = new HashMap<>();
            errToLogMap.put("mac","frontend");
            errToLogMap.put("doiaOfflineServer", "currencyservice");
            errToLogMap.put("nbdServer","cartservice");
            errToLogMap.put("cmdbServer", "recommendationservice");
            errToLogMap.put("eventServer", "shippingservice");
            errToLogMap.put("dodiServer", "paymentservice");
            errToLogMap.put("dolaLogMonitorServer", "emailservice");
            errToLogMap.put("gatewayServer", "checkoutservice");

            try {
                BufferedReader errReader = new BufferedReader(new InputStreamReader(new FileInputStream("/Users/jialichun/本地文稿/dataset/error.csv")));
                String line;
                int k = 0;
                while((line = errReader.readLine()) != null) {
                    String[] arr = line.split(",");
                    String service = errToLogMap.get(arr[4]);
                    if (service == null) {
                        continue;
                    }
                    StringBuilder msg = new StringBuilder(arr[8]);
                    for (int i = 9; i < arr.length; i++) {
                        msg.append(arr[i]);
                    }
                    JSONObject res = JSONObject.parseObject(handleLog(new JsonBuilder()
                            .put("id", arr[1])
                            .put("time", Long.valueOf(arr[2]))
                            .put("message", msg.toString())
                            .put("service", service)
                            .put("logger",service+"."+"Logger")
                            .build()));

                    if(res.getInteger("code") == ConstString.FAILED_DIAGNOSE) {
                        LOG.info("id is " + arr[1]);
                        LOG.info("time is " + arr[2]);
                        LOG.info("message is "+ msg.toString());
                        LOG.info("service is "+ service);
                        return "fail";
                    }
                    k++;
                }
                System.out.println("sum err: " + k);
            } catch(Exception e){
                e.printStackTrace();
                return "fail";
            }
        }
        return "success";
    }

    @RequestMapping(value = "/save_log")
    public String saveLog() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("/Users/jialichun/本地文稿/dataset/training_data_with_faults/tar/cloudbed-1/log/all/log_filebeat-testbed-log-service.csv"), "GBK"));
            String line ;
            int i = 1;
            Map<String, Integer> whiteServer = new HashMap<>();
            whiteServer.put("checkoutservice", 0);
            whiteServer.put("cartservice", 0);
            whiteServer.put("currencyservice", 0);
            whiteServer.put("frontend", 0);
            whiteServer.put("recommendationservice", 0);
            reader.readLine();
            String pre = "currencyservice";
            while ((line = reader.readLine()) != null) {
                String[] item = line.split(",");
                String service = item[2].split("-")[0];
                if (item.length < 5 || whiteServer.get(service) == null || service.equals(pre) ||whiteServer.get(service) >= 10) {
                    pre = service;
                    continue;
                }
                pre = service;
                StringBuilder builder = new StringBuilder(item[4]);
                for (int k = 4; k < item.length; k++) {
                    builder.append(item[k]);
                }
                String msg = builder.toString();
                JSONObject res = JSONObject.parseObject(handleLog(new JsonBuilder()
                        .put("id", item[0])
                        .put("time", Long.parseLong(item[1]) * 1000)
                        .put("message", msg)
                        .put("service", service)
                        .put("logger", item[3])
                        .build()));
                if(res.getInteger("code") == ConstString.SUCCESS_DIAGNOSE_ADD_CATE) {
                    whiteServer.put(service, whiteServer.get(service) + 1);
                    LOG.info("add cate ", service, ", " , whiteServer.get(service));
                }
                if(res.getInteger("code") == ConstString.FAILED_DIAGNOSE) {
                    LOG.info("id is " + item[0]);
                    LOG.info("time is " + item[1]);
                    LOG.info("message is "+ msg);
                    LOG.info("service is "+ service);
                    LOG.info("logger", item[3]);
                    return "fail";
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
        return "success";
    }

    @RequestMapping(value = "/save_log_err")
    public String saveLogERR() {
        try {
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("/Users/jialichun/本地文稿/dataset/groundtruth.csv"), "GBK"));
            reader.readLine();
            Set<Long> errTimeSet = new HashSet<>();
            String errStr;
            while ((errStr = reader.readLine()) != null) {
                String[] item = errStr.split(",");
                long time = Long.parseLong(item[0]);
                errTimeSet.add(time);
                errTimeSet.add(time+1);
            }

            reader = new BufferedReader(new InputStreamReader(new FileInputStream("/Users/jialichun/本地文稿/dataset/training_data_with_faults/tar/cloudbed-1/log/all/log_filebeat-testbed-log-service.csv"), "GBK"));
            String line = reader.readLine();
            int i = 1;
            while ((line = reader.readLine()) != null) {
                String[] item = line.split(",");
                String service = item[2].split("-")[0];
                long time = Long.parseLong(item[1]);
                if (item.length < 5 || !errTimeSet.contains(time)) {
                    continue;
                }
                StringBuilder builder = new StringBuilder(item[4]);
                for (int k = 4; k < item.length; k++) {
                    builder.append(item[k]);
                }
                String msg = builder.toString();
                JSONObject res = JSONObject.parseObject(handleLog(new JsonBuilder()
                        .put("id", item[0])
                        .put("time", time * 1000)
                        .put("message", msg)
                        .put("service", service)
                        .put("logger", item[3])
                        .build()));
                if(res.getInteger("code") == ConstString.FAILED_DIAGNOSE) {
                    LOG.info("id is " + item[0]);
                    LOG.info("time is " + item[1]);
                    LOG.info("message is "+ msg);
                    LOG.info("service is "+ service);
                    LOG.info("logger", item[3]);
                    return "fail";
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            return "fail";
        }
        return "success";
    }
}
